package com.mikelouis.test.scala.proginscala.scalacheck

/**
 * 生成器负责ScalaCheck中测试数据的生成, 由类org.scalacheck.Gen表示。如果需要ScalaCheck生成默认不支持的类型的数据,
 * 或者要使用前述的forAll函数为某些类型的特定子集声明属性，那么应该知道如何使用这个类。在Gen对象中包含几个函数可以创建或者修改现有的生成器。
 * 本节会讨论如何使用其中一些函数。更多更详细的参考，请看API scaladoc
 *
 * 生成器可以被简单视为能接收生成参数，并且(可能的)返回生成值的一个函数. 就是说, 类型Gen[T]
 * 可以被认为是类型Gen.Params => Option[T]的一个函数。不过Gen类包含额外的方法使得它具有可以映射(map)生成器、
 * 在for推导式中使用这些方法等功能。不过，从概念上来说，可以简单认为生成器就是函数,Gen类中的组合子能够被用来创建和修改这个生成器函数的行为
 *
 * Created by yuazhu on 4/18/2015.
 */
object Generators {

  import org.scalacheck.Gen

  // 最佳方式是使用org.scalacheck.Gen模块中的生成器组合子来组合创建新的生成器。这些组合字可以在for推导式中组合使用。
  // 假设需要一个含两个随机整数值的元组的生成器，并且元组中一个整数至少式另一个的二倍大
  val myGen = for {
    n <- Gen.choose(10, 20)
    m <- Gen.choose(2 * n, 500)
  } yield (n, m)

  // 可以创建从特定取值集合中选取一个值的生成
  // 函数oneOf创建了一个在每次生成值时从输入参数值中随机选取一个的生成器。也可以使用类 Gen 的 | 操作符来完成同样工作。
  // 请注意，如果有需要，简单值会被隐式转换成生成器(每次都生成这个值)
  val vowel = Gen.oneOf('A', 'E', 'I', 'O', 'U', 'Y')
  //  val vowelbiz: Gen[Char] = 'A' | 'E' | 'I' | 'O' | 'U' | 'Y'

  // 生成的值符合均匀分布，如果需要可以使用frequency频率组合子控制生成值的分布
  val vowelFrequency = Gen.frequency(
    (3, 'A'),
    (4, 'E'),
    (2, 'I'),
    (3, 'O'),
    (1, 'U'),
    (1, 'Y')
  )

  /** Generating Case Classes */
  // ScalaCheck中很容易生成样例类的随机实例

  /*there is a restriction on forward references in blocks: In a statement sequence s1 ...sn making up a block,
  if a simple name in si refers to an entity deﬁned by sj where j >= i,
  then for all sk between and including si and sj,*/

  // 生成整数2叉树
  sealed abstract class Tree

  case class Node(left: Tree, right: Tree, v: Int) extends Tree

  case object Leaf extends Tree

  import org.scalacheck.Arbitrary
  import Gen._

  val genLeaf = Gen.const(Leaf)

  val genNode = for {
    v <- Arbitrary.arbitrary[Int]
    left <- genTree
    right <- genTree
  } yield Node(left, right, v)

  def genTree: Gen[Tree] = oneOf(genLeaf, genNode)

  /** Sized Generators */
  // 在ScalaCheck使用生成器生成一个值时，会向生成器提供一些参数。这些参数中的一个，是一个指定size大小的值，
  // 某些生成器会使用此参数生成值。如果需要在自己的生成器中使用大小size参数，可以使用Gen.sized函数
  /**
   * 这个matrix生成器会使用作为参数传入的生成器创建一个矩阵，这个矩阵的行列数基于生成器的大小size参数。
   * 此生成器使用Gen.listOfN生成了指定长度的序列，这个序列中填充了由传入的参数生成器生成的值
   * @param g generator
   * @tparam T type
   * @return
   */
  def matrix[T](g: Gen[T]): Gen[Seq[Seq[T]]] = Gen.sized { size =>
    val side = scala.math.sqrt(size).asInstanceOf[Int]
    Gen.listOfN(side, Gen.listOfN(side, g))
  }

  /** Conditional Generators */
  // 限定条件的生成器可以使用Gen.suchThat函数以下面的方式来定义
  /**
   * 条件生成器同条件属性的操作方式相像, 如果条件特别苛刻，ScalaCheck可能无法生成足够的值，
   * 进而会报告属性测试结果无法确定。上面的 smallEvenInteger 定义可能没问题，它仅仅丢弃了生成数值的一半。
   * 不过, 最好谨慎使用suchThat条件操作符
   */
  val smallEvenInteger = Gen.choose(0, 200) suchThat (_ % 2 == 0)

  /** Generating Containers */
  // 有一个特殊的生成器, Gen.containerOf, 会生成值的容器如列表和数组。这个生成器接受其他生成器作为参数，负责个别值的生成
  val genIntList = Gen.containerOf[List, Int](Gen.oneOf(1, 3, 5))

  val genStringStream = Gen.containerOf[Stream, String](Gen.alphaStr)

  val genBoolArray = Gen.containerOf[Array, Boolean](true)

  // 默认ScalaCheck支持List, Stream, Set, Array 和 ArrayList (由 java.util)。也可以添加对其它容器的支持，
  // 添加隐式的'Buildable'实例即可。例子参见Buildable.scala

  // 另外还有 Gen.containerOf1 函数可以生成非空的容器，以及Gen.containerOfN函数生成指定大小的容器

  /** The arbitrary Generator */
  // 特殊的生成器org.scalacheck.Arbitrary.arbitrary会生成支持类型的任意值

  import org.scalacheck.Arbitrary

  val evenInteger = Arbitrary.arbitrary[Int] suchThat (_ % 2 == 0)

  val squares = for {
    xs <- Arbitrary.arbitrary[List[Int]]
  } yield xs.map(x => x * x)

  // ScalaCheck使用这个任意生成器是来生成属性参数。多数时候,需要向上面例子一样给这个任意生成器提供值的类型信息,
  // 这是由于Scala经常无法自动推断出值类型。对具有隐式Arbitrary实例的任何类型都可以使用这个 arbitrary 生成器.
  // 前面提到, ScalaCheck默认支持常见类型,不过也可以对不支持的类型定义自己的隐式Arbitrary实例。
  // 下面是ScalaCheck实现的boolean类型的隐式Arbitrary定义
  // implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false))

  // 对需要支持的应用类型 T , 需要定义一个Arbitrary[T] 类型的隐式的 def 或者 val值。使用工厂方法Arbitrary(...)可以创建 Arbitrary 实例。这个方法接受一个类型为 Gen[T] 的参数并返回 Arbitrary[T] 的一个实例

  // 现在，假设有一个自定义类型Tree[T], 需要在应用的声明属性中作为参数使用

  abstract sealed class TreeAbi[T] {
    def merge(t: TreeAbi[T]) = Internal(List(this, t))

    def size: Int = this match {
      case Leaf(_) => 1
      case Internal(children) => (children :\ 0)(_.size + _)
    }
  }

  case class Internal[T](children: Seq[TreeAbi[T]]) extends TreeAbi[T]

  case class Leaf[T](elem: T) extends TreeAbi[T]

  // 在为类型 TreeAbi[T]声明隐式生成器时，也需要假定已经存在类型T的隐式生成器。这可以通过声明一个类型为 Arbitrary[T]的隐式参数来实现, 然后可以使用arbitrary[T]生成器
  implicit def arbTreeAbi[T](implicit a: Arbitrary[T]): Arbitrary[TreeAbi[T]] =
    Arbitrary {
      val genLeaf = for (e <- Arbitrary.arbitrary[T]) yield Leaf(e)

      def genInternal(sz: Int): Gen[TreeAbi[T]] = for {
        n <- Gen.choose(sz / 3, sz / 2)
        c <- Gen.listOfN(n, sizedTreeAbi(sz / 2))
      } yield Internal(c)

      def sizedTreeAbi(sz: Int) =
        if (sz <= 0) genLeaf
        else Gen.frequency((1, genLeaf), (3, genInternal(sz)))

      Gen.sized(sz => sizedTreeAbi(sz))
    }

  // 只要隐式函数arbTreeAbi在作用域中, 就可以像下面这样编写属性
  val propMergeTreeAbi = org.scalacheck.Prop.forAll((t1: TreeAbi[Int], t2: TreeAbi[Int]) => t1.size + t2.size == t1.merge(t2).size)

  /** Collecting Generated Test Data */
  // 可以收集在属性测试中生成的随机测试数据的数据信息。如果需要检查用例分布、确认测试到了所有不同的用例，而不仅仅是简单的用例，那么收集这些数据可以提供些帮助

  // 例如、可能有一个操作列表的方法，这个方法在排序与否的列表上表现不同。
  // 那么确认ScalaCheck是否使用排序列表和非排序列表都对这个方法进行了测试就很重要。我们首先定义一个 ordered 方法用于帮助我们确定列表是否排序
  def ordered(l: List[Int]) = l == l.sortWith(_ > _)

  // 使用 Prop.classify 来收集生成的测试数据中的感兴趣的信息

  import org.scalacheck.Prop._

  val myProp = forAll { l: List[Int] =>
    classify(ordered(l), "ordered") {
      classify(l.length > 5, "large", "small") {
        l.reverse.reverse == l
      }
    }
  }

  // 也可以直接收集数据，使用Prop.collect方法。下面的假设属性中我们仅仅想看看ScalaCheck生成的测试数据是否分布均匀
  val dummyProp = forAll(Gen.choose(1,10)) { n =>
    collect(n) {
      n == n
    }
  }


  def main(args: Array[String]) {
    println(myGen.sample)
    println(genTree.sample)

    // 测试这个属性, 可以看到ScalaCheck打印出来的统计信息
    println(myProp.check)

    // 可以看到，每个数字出现的频率约为 10% , 这似乎是合理的
    println(dummyProp.check)
  }

}
